using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using OhioState.AssetManaged;
using OhioState.Graphics.Proxy;

namespace OhioState.Controls
{
    /// <summary>
    /// This class represents a rectangular panel for 2D transfer function
    /// interface. The markers and selection rectangle are drawn with GDI.
    /// The code is staightforward, though somewhat lengthy.
    /// </summary>
    public class DrawingPanel : Panel
    {
        private TwoDTransferFunctionCtrl transferFunction;
        private Bitmap histogramBitmap;
        private Position[] markerLocations;
        private Color[] markerColors;
        private const int MARKER_SIZE = 12;
        private const int MARKER_SELECTED_SIZE = 10;
        private const int MARKER_COLOR_SIZE = 8;
        private bool isMouseDown;
        private bool isMarkerMoving;
        private bool isRectangleMoving;
        private int selectedMarkerIndex = 0;
        private Bitmap resultBitmap = new Bitmap(256, 256);
        private Position currentMousePosition;

        private TransferFunction2D transferFunction2D;

        public DrawingPanel(TwoDTransferFunctionCtrl transferFunction)
        {
            //necessary for double buffering (eliminates flickering)
            this.SetStyle(
                ControlStyles.AllPaintingInWmPaint | 
                ControlStyles.UserPaint | 
                ControlStyles.OptimizedDoubleBuffer, true);

            this.transferFunction = transferFunction;

            //there are five markers: the first one in the center; others - corners
            //the one in the center is currently not used; I simply don't render it
            markerLocations = new Position[5];
            markerLocations[0] = new Position(0.5f, 0.5f);
            markerLocations[1] = new Position(0.1f, 0.1f);
            markerLocations[2] = new Position(0.1f, 0.9f);
            markerLocations[3] = new Position(0.9f, 0.9f);
            markerLocations[4] = new Position(0.9f, 0.1f);
            markerColors = new Color[5];
            for (int i = 0; i < 5; i++)
            {
                markerColors[i] = Color.FromArgb(255, 255, 255, 255);
            }

            this.MouseDown += new MouseEventHandler(MouseDownHandler);
            this.MouseUp += new MouseEventHandler(MouseUpHandler);
            this.MouseMove += new MouseEventHandler(MouseMoveHandler);
        }

        #region GDI Rendering Routines
        protected override void OnPaint(PaintEventArgs e)
        {
            System.Drawing.Graphics g = e.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias;
            Pen pen = Pens.Black;
            Brush brush2 = new SolidBrush(Color.FromArgb(255, Color.DarkBlue));
            int min = System.Math.Min(this.Width, this.Height);
            g.FillRectangle(brush2, 0, 0, min, min);

            if (histogramBitmap != null)
            {
                if (histogramBitmap.Size.Width != this.Width)
                {
                    histogramBitmap = ResizeBitmap(histogramBitmap, this.Width, this.Width);
                }
                g.DrawImage(histogramBitmap, 0, 0);
            }

            DrawMarkers(g);
        }

        //have to override this method and make it empty when rendering ourselves
        protected override void OnPaintBackground(PaintEventArgs e)
        {
        }

        public Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight)
        {
            Bitmap result = new Bitmap(nWidth, nHeight);
            using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage((Image)result))
                g.DrawImage(b, 0, 0, nWidth, nHeight);
            return result;
        }

        public void SetSelectedMarkerColor(Color newColor)
        {
            markerColors[selectedMarkerIndex] = newColor;
            this.Invalidate();
        }

        private void DrawMarkers(System.Drawing.Graphics g)
        {
            g.SmoothingMode = SmoothingMode.AntiAlias;
            Point[] points = new Point[4];
            for (int i = 0; i < 4; i++)
            {
                points[i] = new Point((int)(markerLocations[i+1].x * this.Width), (int)(markerLocations[i+1].y * this.Height));
            }

            PathGradientBrush pathGradBrush = new PathGradientBrush(points);
            Color[] gradientPathColors = new Color[] { markerColors[1], markerColors[2], markerColors[3], markerColors[4]};
            pathGradBrush.SurroundColors = gradientPathColors;
            pathGradBrush.CenterColor = Color.FromArgb((markerColors[1].A + markerColors[2].A + markerColors[3].A + markerColors[4].A) / 4,
                (markerColors[1].R + markerColors[2].R + markerColors[3].R + markerColors[4].R) / 4,
                (markerColors[1].G + markerColors[2].G + markerColors[3].G + markerColors[4].G) / 4,
                (markerColors[1].B + markerColors[2].B + markerColors[3].B + markerColors[4].B) / 4);
            g.FillPolygon(pathGradBrush, points);

            //update the resulting bitmap that represents 2D transfer function
            resultBitmap = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppPArgb);
            System.Drawing.Graphics resultBitmapGraphics = System.Drawing.Graphics.FromImage(resultBitmap);
            resultBitmapGraphics.Clear(Color.FromArgb(0, 0, 0, 0));
            resultBitmapGraphics.FillPolygon(pathGradBrush, points);

            for (int i = 0; i < markerLocations.Length; i++)
            {
                if (i == 0) continue;//not doing anything with the center marker for now
                Pen pen = Pens.Black;
                Brush whiteBrush = Brushes.White;
                Brush blackBrush = Brushes.Black;
                Brush selectionBrush = Brushes.Red;
                Brush colorBrush = new SolidBrush(markerColors[i]);

                //to make sure marker is visible on any background, 
                //dark or bright, draw two circles - black and white
                g.FillEllipse(blackBrush, markerLocations[i].x * this.Width - MARKER_SIZE / 2,
                    markerLocations[i].y * this.Height - MARKER_SIZE / 2, MARKER_SIZE, MARKER_SIZE);
                g.FillEllipse(whiteBrush, markerLocations[i].x * this.Width - MARKER_COLOR_SIZE / 2,
                    markerLocations[i].y * this.Height - MARKER_COLOR_SIZE / 2, MARKER_COLOR_SIZE, MARKER_COLOR_SIZE);

                if(selectedMarkerIndex == i)
                {
                    g.FillEllipse(selectionBrush, markerLocations[i].x * this.Width - MARKER_SELECTED_SIZE/ 2,
                        markerLocations[i].y * this.Height - MARKER_SELECTED_SIZE / 2, 
                        MARKER_SELECTED_SIZE, MARKER_SELECTED_SIZE);
                }
            }
        }
        #endregion

        #region Mouse Handling Routines
        private void MouseDownHandler(object o, MouseEventArgs args)
        {
            isMouseDown = true;
            if (currentMousePosition == null)
                currentMousePosition = new Position(args.X, args.Y);
            
            currentMousePosition.x = args.X;
            currentMousePosition.y = args.Y;
            MouseMoveHandler(o, args);
        }

        private void MouseUpHandler(object o, MouseEventArgs args)
        {
            isMouseDown = false;
            isMarkerMoving = false;
            isRectangleMoving = false;
            UpdateTransferFunction();
        }

        private void MouseMoveHandler(object o, MouseEventArgs args)
        {
            if (!isMouseDown) return;
            if (args.X < 0 || args.X > this.Width) return;
            if (args.Y < 0 || args.Y > this.Height) return;

            int currentX = args.X;
            int currentY = args.Y;
            int w = this.Width;
            int h = this.Height;

            int cutoff = w;// 200;//120;//select it rather arbitrarily

            //if mouse is close to the marker, drag the marker
            for (int i = 1; i <= 4; i++)
            {
                double distance = (currentX - (int)(markerLocations[i].x * w)) * (currentX - (int)(markerLocations[i].x * w)) +
                (currentY - (int)(markerLocations[i].y * h)) * (currentY - (int)(markerLocations[i].y * h));

                if ((distance < cutoff || (isMarkerMoving && i == selectedMarkerIndex)) && !isRectangleMoving)
                {
                    moveMarker(i, args.X, args.Y);
                    isMarkerMoving = true;
                    return;
                }
            }

            if (isMarkerMoving) return;
            //otherwise - move the whole rectangle
            float deltaX = args.X - currentMousePosition.x;
            float deltaY = args.Y - currentMousePosition.y;
            
            //check whether we're going out of bounds
            bool canRectangleMove = true;
            for (int i = 0; i < markerLocations.Length; i++)
            {
                if (i == 0) continue;//don't deal with center marker for now
                float newX = markerLocations[i].x + deltaX / this.Width;
                float newY = markerLocations[i].y + deltaY / this.Height;
                if (newX > 1 || newX < 0 || newY > 1 || newY < 0)
                    canRectangleMove = false;
            }
            if (canRectangleMove)
            {
                isRectangleMoving = true;
                for (int i = 0; i < markerLocations.Length; i++)
                {
                    markerLocations[i].x += deltaX/this.Width;
                    markerLocations[i].y += deltaY/this.Height;
                }
            }
            currentMousePosition.x = args.X;
            currentMousePosition.y = args.Y;
            this.Invalidate();
        }

        private void moveMarker(int markerIndex, int mouseX, int mouseY)
        {
            float w = this.Width;
            float h = this.Height;
            float delX = ((float)mouseX) / ((float)w) - ((float)currentMousePosition.x) / w;
            float delY = ((float)mouseY) / ((float)h) - ((float)currentMousePosition.y) / h;
            markerLocations[markerIndex].x += delX;
            markerLocations[markerIndex].y += delY;
            currentMousePosition.x = mouseX;
            currentMousePosition.y = mouseY;
            selectedMarkerIndex = markerIndex;
            MarkerSelectionChanged();
            this.Invalidate();
        }
        #endregion

        private void MarkerSelectionChanged()
        {
            transferFunction.MarkerSelectionChanged(selectedMarkerIndex, markerColors[selectedMarkerIndex]);
            if (transferFunction2D == null)
            {
                transferFunction2D = new TransferFunction2D("transferFunction2D");
                AssetManager.Instance.Add(transferFunction2D);
            }
            //UpdateTransferFunction();//slows things down
        }

        //TODO: should this method be removed from this class?
        public void CreateTransferFunction()
        {
            if (transferFunction2D == null)
            {
                transferFunction2D = new TransferFunction2D("transferFunction2D");
                AssetManager.Instance.Add(transferFunction2D);
            }
            UpdateTransferFunction();
        }

        public void SetHistogram(Bitmap histogram)
        {
            histogramBitmap = histogram;
        }

        public void UpdateTransferFunction()
        {
            if (transferFunction2D == null) CreateTransferFunction();
            resultBitmap = ResizeBitmap(resultBitmap, 256, 256);
            transferFunction2D.BeginManipulation();
            transferFunction2D.TextureBitmap = resultBitmap;
            transferFunction2D.EndManipulation();
        }

        private class Position
        {
            public float x;
            public float y;

            public Position(float x, float y)
            {
                this.x = x;
                this.y = y;
            }
        }
    }
}
